"""Property-based tests for music management functionality.

Feature: buyu-walking-app
Testing correctness properties related to music save and metadata.
"""
import pytest
import uuid
from hypothesis import given, strategies as st, settings, HealthCheck
from datetime import datetime
from app.models.music import Music
from app.models.user import User
from app.models.walking_session import WalkingSession
from app.models.poi import POI
from app.models.route import Route
from app.models.location import Location
from app.services.music_service import MusicService


# Strategies for generating test data
@st.composite
def user_ids(draw):
    """Generate valid user IDs."""
    return draw(st.text(min_size=1, max_size=36, alphabet=st.characters(
        whitelist_categories=('Lu', 'Ll', 'Nd'), 
        blacklist_characters='-'
    )))


@st.composite
def file_urls(draw):
    """Generate valid file URLs."""
    protocols = ['http://', 'https://']
    protocol = draw(st.sampled_from(protocols))
    domain = draw(st.text(min_size=5, max_size=20, alphabet=st.characters(
        whitelist_categories=('Ll', 'Nd'),
        blacklist_characters='-'
    )))
    path = draw(st.text(min_size=5, max_size=50, alphabet=st.characters(
        whitelist_categories=('Ll', 'Nd'),
        blacklist_characters='-'
    )))
    return f"{protocol}{domain}.com/music/{path}.mp3"


@st.composite
def durations(draw):
    """Generate valid music durations in seconds."""
    return draw(st.integers(min_value=10, max_value=600))


@st.composite
def metadata_dicts(draw):
    """Generate valid metadata dictionaries."""
    metadata = {}
    
    # Add optional weather context
    if draw(st.booleans()):
        metadata['weather'] = draw(st.sampled_from(['晴', '阴', '多云', '小雨']))
    
    # Add optional mood
    if draw(st.booleans()):
        metadata['mood'] = draw(st.sampled_from(['愉快', '平静', '兴奋', '放松']))
    
    # Add optional context
    if draw(st.booleans()):
        metadata['context'] = draw(st.text(min_size=5, max_size=50))
    
    return metadata


@st.composite
def locations(draw):
    """Generate valid location data."""
    return Location(
        latitude=draw(st.floats(min_value=-90, max_value=90)),
        longitude=draw(st.floats(min_value=-180, max_value=180)),
        altitude=draw(st.one_of(st.none(), st.floats(min_value=-500, max_value=9000))),
        accuracy=draw(st.floats(min_value=0.1, max_value=100))
    )


class TestMusicSaveFunctionalityProperty:
    """Property-based tests for music save functionality."""
    
    @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        user_nickname=user_ids(),
        file_url=file_urls(),
        duration=durations(),
        metadata=metadata_dicts()
    )
    def test_property_16_music_save_functionality(
        self, app, user_nickname, file_url, duration, metadata
    ):
        """
        Feature: buyu-walking-app, Property 16: 音乐保存功能
        Validates: Requirements 3.4
        
        Property: For any user request to save music, the music file should
        be stored to the user's personal music library and be retrievable.
        
        This tests that:
        1. Music can be saved with valid parameters
        2. Saved music is stored in the database
        3. Saved music can be retrieved by ID
        4. Saved music appears in user's library
        5. Music belongs to the correct user
        """
        with app.app_context():
            # Arrange: Create test user with proper UUID
            from app import db
            
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid=f'wx_{uuid.uuid4()}',
                nickname=f'User_{user_nickname[:8]}'
            )
            db.session.add(user)
            db.session.commit()
            
            actual_user_id = user.id
            
            # Act: Save music
            service = MusicService()
            saved_music = service.save_music(
                user_id=actual_user_id,
                file_url=file_url,
                duration=duration,
                metadata=metadata
            )
            
            # Assert: Music should be saved and retrievable
            assert saved_music is not None, \
                "save_music should return a Music object"
            
            assert isinstance(saved_music, Music), \
                "Returned object should be a Music instance"
            
            assert saved_music.id is not None, \
                "Saved music should have an ID"
            
            assert saved_music.user_id == actual_user_id, \
                f"Music should belong to user {actual_user_id}"
            
            assert saved_music.file_url == file_url, \
                "Music should have correct file URL"
            
            assert saved_music.duration == duration, \
                "Music should have correct duration"
            
            # Test retrieval by ID
            retrieved_music = service.get_music_by_id(saved_music.id, actual_user_id)
            
            assert retrieved_music is not None, \
                "Saved music should be retrievable by ID"
            
            assert retrieved_music.id == saved_music.id, \
                "Retrieved music should have same ID"
            
            assert retrieved_music.file_url == file_url, \
                "Retrieved music should have same file URL"
            
            # Test music appears in user library
            user_library = service.get_user_music_library(actual_user_id)
            
            assert len(user_library) > 0, \
                "User library should contain at least one music"
            
            music_ids = [m.id for m in user_library]
            assert saved_music.id in music_ids, \
                "Saved music should appear in user's library"
            
            # Verify music is in database
            db_music = Music.query.get(saved_music.id)
            assert db_music is not None, \
                "Music should exist in database"
            
            assert db_music.user_id == actual_user_id, \
                "Database record should belong to correct user"


class TestMusicMetadataCompletenessProperty:
    """Property-based tests for music metadata completeness."""
    
    @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        user_nickname=user_ids(),
        poi_name=st.text(min_size=3, max_size=30),
        file_url=file_urls(),
        duration=durations(),
        metadata=metadata_dicts(),
        location=locations()
    )
    def test_property_17_music_metadata_completeness(
        self, app, user_nickname, poi_name, file_url, duration, metadata, location
    ):
        """
        Feature: buyu-walking-app, Property 17: 音乐元数据完整性
        Validates: Requirements 3.5
        
        Property: For any saved music record, it should contain metadata
        fields including generation time, location, and context.
        
        This tests that:
        1. Saved music has metadata
        2. Metadata includes generation timestamp
        3. Metadata includes location when session is provided
        4. Metadata includes POI name when POI is provided
        5. Custom metadata is preserved
        """
        with app.app_context():
            # Arrange: Create test user, POI, route, and session with proper UUIDs
            from app import db
            
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid=f'wx_{uuid.uuid4()}',
                nickname=f'User_{user_nickname[:8]}'
            )
            db.session.add(user)
            
            poi = POI(
                id=str(uuid.uuid4()),
                name=poi_name,
                latitude=location.latitude,
                longitude=location.longitude,
                category='风景名胜'
            )
            db.session.add(poi)
            
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=location.latitude - 0.01,
                start_longitude=location.longitude - 0.01,
                end_latitude=location.latitude,
                end_longitude=location.longitude,
                target_poi_id=poi.id,
                distance_meters=1000.0,
                estimated_duration=15
            )
            db.session.add(route)
            
            session = WalkingSession(
                id=str(uuid.uuid4()),
                user_id=user.id,
                route_id=route.id,
                status='active',
                current_latitude=location.latitude,
                current_longitude=location.longitude
            )
            db.session.add(session)
            db.session.commit()
            
            actual_user_id = user.id
            actual_session_id = session.id
            actual_poi_id = poi.id
            
            # Act: Save music with session and POI
            service = MusicService()
            saved_music = service.save_music(
                user_id=actual_user_id,
                file_url=file_url,
                duration=duration,
                session_id=actual_session_id,
                poi_id=actual_poi_id,
                metadata=metadata
            )
            
            # Assert: Metadata should be complete
            assert saved_music.meta_info is not None, \
                "Music should have metadata"
            
            assert isinstance(saved_music.meta_info, dict), \
                "Metadata should be a dictionary"
            
            # Check for generation timestamp
            assert 'generated_at' in saved_music.meta_info, \
                "Metadata should include generation timestamp"
            
            generated_at = saved_music.meta_info['generated_at']
            assert generated_at is not None, \
                "Generation timestamp should not be None"
            
            # Verify timestamp is valid ISO format
            try:
                datetime.fromisoformat(generated_at)
            except (ValueError, TypeError):
                pytest.fail("Generation timestamp should be valid ISO format")
            
            # Check for location metadata (from session)
            assert 'location' in saved_music.meta_info, \
                "Metadata should include location from session"
            
            location_meta = saved_music.meta_info['location']
            assert 'latitude' in location_meta, \
                "Location metadata should include latitude"
            
            assert 'longitude' in location_meta, \
                "Location metadata should include longitude"
            
            assert location_meta['latitude'] == location.latitude, \
                "Location latitude should match session location"
            
            assert location_meta['longitude'] == location.longitude, \
                "Location longitude should match session location"
            
            # Check for POI name
            assert 'poi_name' in saved_music.meta_info, \
                "Metadata should include POI name"
            
            assert saved_music.meta_info['poi_name'] == poi_name, \
                "POI name in metadata should match actual POI"
            
            # Check custom metadata is preserved
            for key, value in metadata.items():
                assert key in saved_music.meta_info, \
                    f"Custom metadata key '{key}' should be preserved"
                
                assert saved_music.meta_info[key] == value, \
                    f"Custom metadata value for '{key}' should match"
            
            # Verify music is linked to session and POI
            assert saved_music.session_id == actual_session_id, \
                "Music should be linked to session"
            
            assert saved_music.poi_id == actual_poi_id, \
                "Music should be linked to POI"
